package visitor

import (
	"bytes"
	"dc/context"
	"dc/util"
	"errors"
	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

type tableVisitor struct{}

func (t *tableVisitor) Visit(ctx *context.Context, node *html.Node) error {
	data, ok := util.GetAttr(node, TData)
	if !ok {
		return nil
	}
	alias, ok := util.GetAttr(node, TRange)
	if !ok {
		return nil
	}
	list := util.ToList(ctx.Get(data))

	// find elements
	tbody := util.FindChild(node, atom.Tbody)
	if tbody == nil {
		return errors.New("not found tbody in table. ")
	}
	trs := util.FindChildren(tbody, atom.Tr)
	if len(trs) == 0 {
		return nil
	}

	var header, loop *html.Node
	var transposeIndex = -1
	var transposeDimension, transposeValue string
	var transposeGroup []string

	// transpose
	next := trs[0]
	for next != nil {
		if util.HasAttr(next, THeader) {
			header = next
		} else if util.HasAttr(next, TLoop) {
			loop = next
		}
		next = next.NextSibling
	}

	headerFields := util.FindChildren(header, atom.Td)
	loopFields := util.FindChildren(loop, atom.Td)
	for i, td := range headerFields {
		v1, ok1 := util.GetAttr(td, TTransposeDimension)
		v2, ok2 := util.GetAttr(td, TTransposeValue)
		if ok1 && ok2 {
			transposeIndex = i
			transposeDimension = v1
			transposeValue = v2
			continue
		}
		transposeGroup = append(transposeGroup, util.GetContent(loopFields[i]))
	}
	if transposeIndex > -1 {
		key := bytes.Buffer{}
		transposeData := map[string]map[string]interface{}{}
		transposeDimensions := map[string]bool{}
		for _, item := range list {
			subCtx := ctx.Fork(alias, item)
			for _, field := range transposeGroup {
				key.WriteString(subCtx.Parse(field))
				key.WriteByte('_')
			}
			val, exist := transposeData[key.String()]
			if !exist {
				val = util.CloneMap(util.ToMap(item))
				transposeData[key.String()] = val
			}
			dimension := TransposeMark + util.ToString(subCtx.Find(transposeDimension))
			transposeDimensions[dimension] = true
			val[dimension] = util.ToFloat(val[dimension]) + util.ToFloat(subCtx.Find(transposeValue))
			key.Reset()
		}

		headerField := headerFields[transposeIndex]
		loopField := loopFields[transposeIndex]
		for dimension := range transposeDimensions {
			newNode := util.CloneNode(headerField)
			util.SetContent(newNode, dimension[len(TransposeMark):])
			header.InsertBefore(newNode, headerField)
			newNode = util.CloneNode(loopField)
			util.SetContent(newNode, context.BuildExpr(alias, dimension))
			loop.InsertBefore(newNode, loopField)
		}
		header.RemoveChild(headerField)
		loop.RemoveChild(loopField)

		list = list[:0]
		for _, m := range transposeData {
			list = append(list, m)
		}
	}

	// render grid
	next = trs[0]
	for next != nil {
		if util.HasAttr(next, TLoop) {
			for _, item := range list {
				subCtx := ctx.Fork(alias, item)
				newNode := util.CloneNode(next)
				cells := util.FindChildren(newNode, atom.Td)
				for _, cell := range cells {
					util.SetContent(cell, subCtx.Parse(util.GetContent(cell)))
				}
				tbody.InsertBefore(newNode, next)
			}
			tmp := next.NextSibling
			tbody.RemoveChild(next)
			next = tmp
			continue
		}
		cells := util.FindChildren(next, atom.Td)
		for _, cell := range cells {
			util.SetContent(cell, ctx.Parse(util.GetContent(cell)))
		}
		next = next.NextSibling
	}
	return nil
}
